#include "L2Configurator.hpp"
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>

extern "C" size_t strnlen (const char *__string, size_t __maxlen);

const char *Configurator::str_TRUE = "true";
const char *Configurator::str_FALSE = "false";

/************
 *  @fn     Configurator::Configurator(FileAdapter *file_adapter)
 *  @brief  Constructor of configurator.
 *  @param  file_adapter    File adapter of Configurator.
 */
Configurator::Configurator(DataSet *dataset,FileAdapter *file_adapter){
    this->dataset = dataset;
    this->file_adapter = file_adapter;
}

/************
 *  @fn     Configurator::~Configurator()
 *  @brief  Destructor of configurator.
 */
Configurator::~Configurator(){ }

bool Configurator::add(const char *label,bool value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->add(label,value);
}

bool Configurator::add(const char *label,double value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->add(label,value);
}

bool Configurator::add(const char *label,const char *value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->add(label,value);
}

bool Configurator::remove(const char *label){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->remove(label);
}

bool Configurator::set(const char *label,bool value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->set(label,value);
}

bool Configurator::set(const char *label,double value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->set(label,value);
}

bool Configurator::set(const char *label,const char *value){
    if ( this->dataset == nullptr ){
        return false;
    }
    this->modify_flag = true;
    return this->dataset->set(label,value);
}

bool Configurator::getBoolean(const char *label){
    if ( this->dataset == nullptr ){
        return false;
    }
    return this->dataset->getBoolean(label);
}

double Configurator::getNumber(const char *label){
    if ( this->dataset == nullptr ){
        return 0.0;
    }
    return this->dataset->getNumber(label);
}

const char *Configurator::getString(const char *label){
    if ( this->dataset == nullptr ){
        return nullptr;
    }
    return this->dataset->getString(label);
}

bool Configurator::hasLabel(const char *label){
    if ( this->dataset == nullptr ){
        return false;
    }
    return this->dataset->hasLabel(label);
}

DataSet::DataType Configurator::getDataType(const char *label){
    if ( this->dataset == nullptr ){
        return DataType::Invaild;
    }
    return this->dataset->getDataType(label);
}

bool Configurator::clear(void){
    if ( this->dataset == nullptr ){
        return false;
    }
    return this->dataset->clear();
}

int Configurator::getDataCount(void){
    if ( this->dataset == nullptr ){
        return 0;
    }
    return this->dataset->getDataCount();
}

struct DataSet::Data *Configurator::getDataItem(int pos){
    if ( this->dataset == nullptr ){
        return nullptr;
    }
    return this->dataset->getDataItem(pos);
}

/************
 *  @fn     bool Configurator::load(void)
 *  @brief  Load configuration from file.
 *  @return Load operation result. true - Load success. false - Load failed.
 */
bool Configurator::load(void){
    if ( this->file_adapter == nullptr ){
        this->error_number = Error_NoFileAdapter;
        return false;
    }
    if ( this->dataset == nullptr ){
        this->error_number = Error_NoDataSet;
    }
    if ( !this->file_adapter->open(FileAdapter::Flag_Read) ){
        this->error_number = Error_FileOpenFailed;
        return false;
    }
    char line_buffer[PORT_MAX_LINE_LENGTH];

    char *label = nullptr;
    char *value = nullptr;
    do {
        label = nullptr;
        value = nullptr;
        memset(line_buffer,0,sizeof(line_buffer));
        unsigned int str_length = this->file_adapter->readline(line_buffer);
        if ( str_length == 0 ){
            break;
        }
        /* Trim line. */
        int str_trimmed_length = 0;
        int quota_mark = 0;
        for ( unsigned int n = 0; n < str_length; n++ ){
            /* Process comment. */
            if ( line_buffer[n] == '#' ){
                break;
            }else if ( line_buffer[n] != ' ' && line_buffer[n] != '"' && line_buffer[n] != '\r' && line_buffer[n] != '\n' ){
                line_buffer[str_trimmed_length++] = line_buffer[n];
            }else if ( line_buffer[n] == '"' ){
                quota_mark += 1;
                line_buffer[str_trimmed_length++] = line_buffer[n];
            }else if ( line_buffer[n] == ' ' ){
                if ( quota_mark == 1 ){
                    line_buffer[str_trimmed_length++] = line_buffer[n];
                }
            }
        }
        line_buffer[str_trimmed_length] = '\0';
        /* Get label and value. */
        label = line_buffer;
        for ( int n = 0; n < str_trimmed_length; n++ ){
            if ( line_buffer[n] == '=' ){
                line_buffer[n] = '\0';
                value = line_buffer + n + 1;
                break;
            }
        }
        /* Check line available. */
        if ( value == nullptr ){
            continue;   /* Not a vaild line. */
        }
        if ( strlen(label) * strlen(value) == 0 ){
            continue;   /* Not a vaild line. */
        }
        /* Add data. */
        if ( value[0] == '\"' ){
            /* String data. */
            value += 1;
            str_length = strlen(value);
            for ( unsigned int n = 0; n < str_length; n++ ){
                if ( value[n] == '\"' ){
                    value[n] = '\0';
                    break;
                }
            }
            /* Check label existance. */
            if ( this->dataset->hasLabel(label) ){
                this->dataset->set(label,value);
            }else{
                this->dataset->add(label,value);
            }
        }else{
            /* Data maybe a boolean data or number data. */
            str_length = strlen(value);
            for ( unsigned int n = 0; n < str_length; n++ ){
                value[n] = tolower(value[n]);
            }
            if ( strcmp(value,Configurator::str_TRUE) == 0 ){
                if ( this->dataset->hasLabel(label) ){
                    this->dataset->set(label,true);
                }else{
                    this->dataset->add(label,true);
                }
            }else if ( strcmp(value,Configurator::str_FALSE) == 0 ){
                if ( this->dataset->hasLabel(label) ){
                    this->dataset->set(label,false);
                }else{
                    this->dataset->add(label,false);
                }
            }else{
                /* Maybe a number data. */
                int dot_cnt = 0;
                unsigned int n = 0;
                for ( n = 0; n < str_length; n++ ){
                    if ( value[n] > '9' || value[n] < '0' ){
                        if ( value[n] == '.' ){
                            if ( dot_cnt < 1 ){
                                dot_cnt += 1;
                            }else{
                                /* Value string has more than 1 dot character. Not a vaild number string. */
                                break;
                            }
                        }else if ( value[n] == '-' ){
                            if ( n != 0 ){
                                break;
                            }
                        }else{
                            /* Value string has character which not number or dot. Not a vaild number string. */
                            break;
                        }
                    }
                }
                if ( n == str_length ){
                    /* Value string is vaild number string. */
                    double number_value = atof(value);
                    if ( this->dataset->hasLabel(label) ){
                        this->dataset->set(label,number_value);
                    }else{
                        this->dataset->add(label,number_value);
                    }
                }
            }
        }
        /* Line process finished. */
    } while ( true );
    if ( !this->file_adapter->close() ){
        this->error_number = Error_FileCloseFailed;
        return false;
    }
    this->error_number = Error_None;
    return true;
}

/************
 *  @fn     bool Configurator::store(void)
 *  @brief  Save configurations to file. This method will overwrite origin file. If file contains comment that will be discard.
 *  @return Save operation result. true - Save success. false - Save failed.
 */
bool Configurator::store(void){
    if ( this->file_adapter == nullptr ){
        this->error_number = Error_NoFileAdapter;
        return false;
    }
    if ( this->dataset == nullptr ){
        this->error_number = Error_NoDataSet;
    }
    if ( !this->file_adapter->open(FileAdapter::Flag_Write) ){
        this->error_number = Error_FileOpenFailed;
        return false;
    }

    char content_buffer[8192] = {0};
    memset(content_buffer,0,sizeof(content_buffer));

    Data *data = nullptr;
    char line_buffer[PORT_MAX_LINE_LENGTH + 1];

    for ( int index = 0; index < this->dataset->getDataCount(); index++ ){
        memset(line_buffer,0,sizeof(line_buffer));
        /* Get data item. */
        data = this->dataset->getDataItem(index);
        if ( data == nullptr ) continue;
        if ( data->data_type == DataType::Invaild ) continue;
        if ( data->data_type == DataType::Boolean ){
            bool boolean_value = false;
            memcpy(&boolean_value,data->value,sizeof(bool));
            snprintf(line_buffer,PORT_MAX_LINE_LENGTH,"%s=%s\n",data->label,boolean_value ? str_TRUE : str_FALSE);
        }else if ( data->data_type == DataType::Number ){
            double number_value = 0.0;
            memcpy(&number_value,data->value,sizeof(double));
            snprintf(line_buffer,PORT_MAX_LINE_LENGTH,"%s=%.8f\n",data->label,number_value);
        }else if ( data->data_type == DataType::String ){
            snprintf(line_buffer,PORT_MAX_LINE_LENGTH,"%s=\"%s\"\n",data->label,(char*)data->value);
        }
        
        strncat(content_buffer,line_buffer,sizeof(content_buffer) - 1);

    }

    if ( this->file_adapter->writeline(content_buffer) < strnlen(content_buffer,sizeof(content_buffer)) ){
        this->error_number = Error_FileWriteError;
        this->file_adapter->close();
        return false;
    }

    if ( !this->file_adapter->close() ){
        this->error_number = Error_FileCloseFailed;
        return false;
    }
    this->error_number = Error_None;
    return true;
}

bool Configurator::storeModified(void){
    bool ret = false;
    if ( this->modify_flag ){
        ret = this->store();
    }
    this->modify_flag = false;
    return ret;
}

